﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using Core.ObjectOperate;

namespace Core.Tools
{
    public static class EmitHelper
    {
        /// <summary>
        /// 将指定索引处的变量的地址推送到堆栈中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Ldloca(this ILGenerator ILGen, int Index)
        {
            if ((uint)Index <= 255)
            {
                ILGen.Emit(OpCodes.Ldloca_S, (byte)Index);
            }
            else
            {
                ILGen.Emit(OpCodes.Ldloca, Index);

            }
        }

        /// <summary>
        /// 将指定索引处的参数的地址推送到堆栈中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Ldarga(this ILGenerator ILGen, int Index)
        {
            if (Index <= 255)
            {
                ILGen.Emit(OpCodes.Ldarga_S, (byte)Index);
            }
            else
            {
                ILGen.Emit(OpCodes.Ldarga, Index);
            }
        }

        /// <summary>
        /// 将一个 32 位整数推送到堆栈中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Value">32 位整数值</param>
        public static void Emit_Ldc_I4(this ILGenerator ILGen, int Value)
        {
            switch (Value)
            {
                case -1:
                    ILGen.Emit(OpCodes.Ldc_I4_M1);
                    break;
                case 0:
                    ILGen.Emit(OpCodes.Ldc_I4_0);
                    break;
                case 1:
                    ILGen.Emit(OpCodes.Ldc_I4_1);
                    break;
                case 2:
                    ILGen.Emit(OpCodes.Ldc_I4_2);
                    break;
                case 3:
                    ILGen.Emit(OpCodes.Ldc_I4_3);
                    break;
                case 4:
                    ILGen.Emit(OpCodes.Ldc_I4_4);
                    break;
                case 5:
                    ILGen.Emit(OpCodes.Ldc_I4_5);
                    break;
                case 6:
                    ILGen.Emit(OpCodes.Ldc_I4_6);
                    break;
                case 7:
                    ILGen.Emit(OpCodes.Ldc_I4_7);
                    break;
                case 8:
                    ILGen.Emit(OpCodes.Ldc_I4_8);
                    break;
                default:
                    if ((uint)Value <= 255)
                    {
                        ILGen.Emit(OpCodes.Ldc_I4_S, (byte)Value);
                    }
                    else
                    {
                        ILGen.Emit(OpCodes.Ldc_I4, Value);
                    }
                    break;
            }
        }

        /// <summary>
        /// 将堆栈上的值弹出到指定索引处的变量中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Stloc(this ILGenerator ILGen, int Index)
        {
            switch (Index)
            {
                case 0:
                    ILGen.Emit(OpCodes.Stloc_0);
                    break;
                case 1:
                    ILGen.Emit(OpCodes.Stloc_1);
                    break;
                case 2:
                    ILGen.Emit(OpCodes.Stloc_2);
                    break;
                case 3:
                    ILGen.Emit(OpCodes.Stloc_3);
                    break;
                default:
                    if ((uint)Index <= 255)
                    {
                        ILGen.Emit(OpCodes.Stloc_S, (byte)Index);
                    }
                    else
                    {
                        ILGen.Emit(OpCodes.Stloc, Index);
                    }
                    break;
            }
        }

        /// <summary>
        /// 将指定索引处的变量推送到堆栈中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Ldloc(this ILGenerator ILGen, int Index)
        {
            switch (Index)
            {
                case 0:
                    ILGen.Emit(OpCodes.Ldloc_0);
                    break;
                case 1:
                    ILGen.Emit(OpCodes.Ldloc_1);
                    break;
                case 2:
                    ILGen.Emit(OpCodes.Ldloc_2);
                    break;
                case 3:
                    ILGen.Emit(OpCodes.Ldloc_3);
                    break;
                default:
                    if ((uint)Index <= 255)
                    {
                        ILGen.Emit(OpCodes.Ldloc_S, (byte)Index);
                    }
                    else
                    {
                        ILGen.Emit(OpCodes.Ldloc, Index);
                    }
                    break;
            }
        }

        /// <summary>
        /// 将指定索引处的参数推送到堆栈中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Ldarg(this ILGenerator ILGen, int Index)
        {
            switch (Index)
            {
                case 0:
                    ILGen.Emit(OpCodes.Ldarg_0);
                    break;
                case 1:
                    ILGen.Emit(OpCodes.Ldarg_1);
                    break;
                case 2:
                    ILGen.Emit(OpCodes.Ldarg_2);
                    break;
                case 3:
                    ILGen.Emit(OpCodes.Ldarg_3);
                    break;
                default:
                    if ((uint)Index <= 255)
                    {
                        ILGen.Emit(OpCodes.Ldarg_S, (byte)Index);
                    }
                    else
                    {
                        ILGen.Emit(OpCodes.Ldarg, Index);
                    }
                    break;
            }
        }

        /// <summary>
        /// 将堆栈上的值弹出到指定索引处的参数中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Index">变量索引</param>
        public static void Emit_Starg(this ILGenerator ILGen, int Index)
        {
            if ((uint)Index <= 255)
            {
                ILGen.Emit(OpCodes.Starg_S, (byte)Index);
            }
            else
            {
                ILGen.Emit(OpCodes.Starg, Index);
            }
        }


        /// <summary>
        /// 将堆栈上的对象转换成制定类型，自动判断值类型。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Type">目标类型</param>
        public static void Emit_Cast(this ILGenerator ILGen, Type Type)
        {
            if (Type.IsValueType)
            {
                ILGen.Emit(OpCodes.Unbox_Any, Type);
            }
            else
            {
                ILGen.Emit(OpCodes.Castclass, Type);
            }
        }

        /// <summary>
        /// 将指定类型的值写入到指定地址的内存中。
        /// </summary>
        /// <param name="ILGen">IL 代码生成器</param>
        /// <param name="Type">目标类型</param>
        public static void Emit_Stobj(this ILGenerator ILGen, Type Type)
        {
            switch (Type.GetTypeCode(Type))
            {
                case TypeCode.Boolean:
                case TypeCode.Byte:
                    ILGen.Emit(OpCodes.Stind_I1);
                    return;
                case TypeCode.Char:
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                    ILGen.Emit(OpCodes.Stind_I2);
                    return;
                case TypeCode.Int32:
                case TypeCode.UInt32:
                    ILGen.Emit(OpCodes.Stind_I4);
                    return;
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    ILGen.Emit(OpCodes.Stind_I8);
                    return;
                case TypeCode.Single:
                    ILGen.Emit(OpCodes.Stind_R4);
                    return;
                case TypeCode.Double:
                    ILGen.Emit(OpCodes.Stind_R8);
                    return;
            }

            if (Type.IsPointer || Type == typeof(IntPtr))
            {
                ILGen.Emit(OpCodes.Stind_I);
            }
            else if (Type.IsValueType)
            {
                ILGen.Emit(OpCodes.Stobj, Type);
            }
            else
            {
                ILGen.Emit(OpCodes.Stind_Ref);
            }


        }

        public static void Emit_Switch(this ILGenerator ILGen,
            Action<ILGenerator> LdcValue, 
            CaseInfo<string>[] Cases, 
            Label DefaultLabel)
        {
            Emit_Switch(
                ILGen,
                LdcValue, 
                ((Func<string, uint>)StringHelper.ComputeStringHash).Method,
                ((Func<string, string, bool>)string.Equals).Method,
                Cases,
                DefaultLabel);
        }

        public static void Emit_Switch(this ILGenerator ILGen,
            Action<ILGenerator> LdValue,
            CaseInfo<int>[] Cases,
            Label DefaultLabel)
        {
            Cases = (CaseInfo<int>[])Cases.Clone();

            Array.Sort(Cases, (Before, After)=> { return Before.Value - After.Value; });

            Emit_SwitchNumber(ILGen, LdValue, Cases, DefaultLabel, 0, (Cases.Length - 1) / 2, Cases.Length - 1);
        }

        public static void Emit_Switch<T>(this ILGenerator ILGen,
            Action<ILGenerator> LdcValue,
            MethodInfo ComputeStringHash,
            MethodInfo EqualsMethod,
            CaseInfo<T>[] Cases,
            Label DefaultLabel)
        {
            Cases = (CaseInfo<T>[])Cases.Clone();

            Array.Sort(Cases);

            var GroupedCases = new ParamsDictionary<int, List<CaseInfo<T>>>();

            foreach (var Item in Cases)
            {
                List<CaseInfo<T>> Items;

                GroupedCases.TryGetValue(Item.HashCode, out Items);

                if (Items == null)
                {
                    Items = new List<CaseInfo<T>>();

                    Items.Add(Item);

                    GroupedCases.Add(Item.HashCode, Items);
                }
                else
                {
                    Items.Add(Item);
                }
            }

            var HashCodeLocal = ILGen.DeclareLocal(typeof(int));

            LdcValue(ILGen);
            ILGen.Emit(OpCodes.Call, ComputeStringHash);
            ILGen.Emit_Stloc(HashCodeLocal.LocalIndex);

            Emit_SwitchObject(
                ILGen,
                LdcValue,
                HashCodeLocal,
                (EqILGen) =>
                {
                    EqILGen.Emit(OpCodes.Call, EqualsMethod);
                },
                GroupedCases,
                DefaultLabel,
                0,
                (GroupedCases.Count - 1) / 2,
                GroupedCases.Count - 1);
        }


        private static void Emit_SwitchNumber<T>(this ILGenerator ILGen,
            Action<ILGenerator> LdValue,
            CaseInfo<T>[] Cases,
            Label DefaultLabel,
            int Begin,
            int Index,
            int End)
        {
            if (Begin == End)
            {
                LdValue(ILGen);
                Cases[Begin].LdcValue(ILGen, Cases[Begin].Value);
                ILGen.Emit(OpCodes.Beq, Cases[Begin].Label);
                ILGen.Emit(OpCodes.Br, DefaultLabel);

                return;
            }

            var GtLabel = ILGen.DefineLabel();

            LdValue(ILGen);
            Cases[Index].LdcValue(ILGen, Cases[Index].Value);
            ILGen.Emit(OpCodes.Bgt, GtLabel);

            Emit_SwitchNumber(ILGen, LdValue, Cases, DefaultLabel, Begin, (Begin + Index) / 2, Index);

            ILGen.MarkLabel(GtLabel);

            Emit_SwitchNumber(ILGen, LdValue, Cases, DefaultLabel, Index + 1, (Index + 1 + End) / 2, End);
        }

        private static void Emit_SwitchObject<T>(this ILGenerator ILGen,
            Action<ILGenerator> LdcValue,
            LocalBuilder HashCodeLocal,
            Action<ILGenerator> CallEquals,
            ParamsDictionary<int, List<CaseInfo<T>>> Cases,
            Label DefaultLabel,
            int Begin,
            int Index,
            int End)
        {
            if (Begin == End)
            {
                ILGen.Emit_Ldloc(HashCodeLocal.LocalIndex);
                ILGen.Emit_Ldc_I4(Cases[Begin].Key);
                ILGen.Emit(OpCodes.Bne_Un, DefaultLabel);

                foreach (var Item in Cases[Begin].Value)
                {
                    LdcValue(ILGen);
                    Item.LdcValue(ILGen, Item.Value);
                    CallEquals(ILGen);
                    ILGen.Emit(OpCodes.Brtrue, Item.Label);
                }

                ILGen.Emit(OpCodes.Br, DefaultLabel);

                return;
            }

            var GtLabel = ILGen.DefineLabel();

            ILGen.Emit_Ldloc(HashCodeLocal.LocalIndex);
            ILGen.Emit_Ldc_I4(Cases[Index].Key);
            ILGen.Emit(OpCodes.Bgt, GtLabel);

            Emit_SwitchObject(
                ILGen, 
                LdcValue,
                HashCodeLocal,
                CallEquals,
                Cases,
                DefaultLabel,
                Begin, 
                (Begin + Index) / 2,
                Index);

            ILGen.MarkLabel(GtLabel);

            Emit_SwitchObject(
                ILGen,
                LdcValue,
                HashCodeLocal,
                CallEquals,
                Cases,
                DefaultLabel,
                Index + 1,
                (Index + 1 + End) / 2,
                End);
        }
    }

    public class CaseInfo<T> : IComparable<CaseInfo<T>>
    {
        public readonly T Value;
        public readonly Label Label;
        public readonly int HashCode;
        public readonly Action<ILGenerator, T> LdcValue;

        public CaseInfo(T Value, Label Label)
        {
            this.Value = Value;
            this.Label = Label;

            if (typeof(T) == typeof(string))
            {
                LdcValue = (ILGen, StringValue) =>
                {
                    ILGen.Emit(OpCodes.Ldstr, (string)(object)StringValue);
                };

                HashCode = (int)StringHelper.ComputeStringHash(((string)(object)Value));
            }
            else if (typeof(T) == typeof(int))
            {
                LdcValue = (ILGen, IntValue) =>
                {
                    ILGen.Emit_Ldc_I4((int)(object)IntValue);
                };

                HashCode = (int)(object)Value;
            }
            else
            {
                throw new Exception("不能自动识别 LdcValue，需要自行配置。");
            }
        }
        public CaseInfo(T Value, int HashCode, Label Label, Action<ILGenerator, T> LdcValue)
        {
            this.Value = Value;
            this.Label = Label;
            this.HashCode = HashCode;

            this.LdcValue = LdcValue;
        }

        public int CompareTo(CaseInfo<T> Other)
        {
            return HashCode.CompareTo(Other.HashCode);
        }
    }
}